{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "95f0a171",
   "metadata": {},
   "source": [
    "(code-basics)=\n",
    "# Basics of Coding\n",
    "\n",
    "In this chapter, you'll learn about the basics of objects, types, operations, conditions, loops, functions, and imports. These are the basic building blocks of almost all programming languages.\n",
    "\n",
    "This chapter has benefited from the excellent [*Python Programming for Data Science*](https://www.tomasbeuzen.com/python-programming-for-data-science/README.html) book by Tomas Beuzen.\n",
    "\n",
    "```{tip}\n",
    "Remember, you can launch this page interactively by using the 'Colab' or 'Binder' buttons under the rocket symbol at the top of the page. You can also download this page as a Jupyter Notebook to run on your own computer: use the 'download .ipynb' button under the download symbol the top of the page.\n",
    "```\n",
    "\n",
    "## If you get stuck\n",
    "\n",
    "It's worth saying at the outset that *no-one* memorises half of the stuff you'll see in this book. 80% or more of time spent programming is actually time spent looking up how to do this or that online, 'debugging' a code for errors, or testing code. This applies to all programmers, regardless of level. You are here to learn the skills and concepts of programming, not the precise syntax (which is easy to look up later).\n",
    "\n",
    "![xkcd-what-did-you-see](https://imgs.xkcd.com/comics/wisdom_of_the_ancients.png)\n",
    "\n",
    "Knowing how to Google is one of the most important skills of any coder. No-one remembers every function from every library. Here are some useful coding resources:\n",
    "\n",
    "- when you have an error, look on Stack Overflow to see if anyone else had the same error (they probably did) and how they overcame it.\n",
    "\n",
    "- if you're having trouble navigating a new package or library, look up the documentation online. The best libraries put as much effort into documentation as they do the code base.\n",
    "\n",
    "- use cheat sheets to get on top of a range of functionality quickly. For instance, this excellent (mostly) base Python [Cheat Sheet](https://gto76.github.io/python-cheatsheet/).\n",
    "\n",
    "- if you're having a coding issue, take a walk to think about the problem, or explain your problem to an animal toy on your desk ([traditionally](https://en.wikipedia.org/wiki/Rubber_duck_debugging) a rubber duck, but other animals are available).\n",
    "\n",
    "## Values, variables, and types\n",
    "\n",
    "A value is datum such as a number or text. There are different types of values: 352.3 is known as a float or double, 22 is an integer, and \"Hello World!\" is a string. A variable is a name that refers to a value: you can think of a variable as a box that has a value, or multiple values, packed inside it. \n",
    "\n",
    "Almost any word can be a variable name as long as it starts with a letter or an underscore, although there are some special keywords that can't be used because they already have a role in the Python language: these include `if`, `while`, `class`, and `lambda`.\n",
    "\n",
    "Creating a variable in Python is achieved via an assignment (putting a value in the box), and this assignment is done via the `=` operator. The box, or variable, goes on the left while the value we wish to store appears on the right. It's simpler than it sounds:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "209ef434",
   "metadata": {},
   "outputs": [],
   "source": [
    "a = 10\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8d776ae",
   "metadata": {},
   "source": [
    "This creates a variable `a`, assigns the value 10 to it, and prints it. Sometimes you will hear variables referred to as *objects*. Everything that is not a literal value, such as `10`, is an object. In the above example, `a` is an object that has been assigned the value `10`.\n",
    "\n",
    "How about this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "63c32d9b",
   "metadata": {},
   "outputs": [],
   "source": [
    "b = \"This is a string\"\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95e31232",
   "metadata": {},
   "source": [
    "It's the same thing but with a different **type** of data, a string instead of an integer. Python is *dynamically typed*, which means it will guess what type of variable you're creating as you create it. This has pros and cons, with the main pro being that it makes for more concise code.\n",
    "\n",
    "```{admonition} Important\n",
    "Everything is an object, and every object has a type.\n",
    "```\n",
    "\n",
    "The most basic built-in data types that you'll need to know about are: integers `10`, floats `1.23`, strings `like this`, booleans `True`, and nothing `None`. Python also has a built-in type called a list `[10, 15, 20]` that can contain anything, even *different* types. So"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad23883a",
   "metadata": {},
   "outputs": [],
   "source": [
    "list_example = [10, 1.23, \"like this\", True, None]\n",
    "print(list_example)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "440240ca",
   "metadata": {},
   "source": [
    "is completely valid code. `None` is a special type of nothingness, and represents an object with no value. It has type `NoneType` and is more useful than you might think! \n",
    "\n",
    "As well as the built-in types, packages can define their own custom types. If you ever want to check the type of a Python variable, you can call the `type` function on it like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "46a517ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "type(list_example)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a78274c2",
   "metadata": {},
   "source": [
    "This is especially useful for debugging `ValueError` messages.\n",
    "\n",
    "Below is a table of common data types in Python:\n",
    "\n",
    "| Name          | Type name  | Type Category  | Description                                   | Example                                    |\n",
    "| :-------------------- | :--------- | :------------- | :-------------------------------------------- | :----------------------------------------- |\n",
    "| integer               | `int`      | Numeric Type   | positive/negative whole numbers               | `22`                                       |\n",
    "| floating point number | `float`    | Numeric Type   | real number in decimal form                   | `3.14159`                                  |\n",
    "| boolean               | `bool`     | Boolean Values | true or false                                 | `True`                                     |\n",
    "| string                | `str`      | Sequence Type  | text                                          | `\"Hello World!\"`                 |\n",
    "| list                  | `list`     | Sequence Type  | a collection of objects - mutable & ordered   | `['text entry', True, 16]`               |\n",
    "| tuple                 | `tuple`    | Sequence Type  | a collection of objects - immutable & ordered | `(51.02, -0.98)`                 |\n",
    "| dictionary            | `dict`     | Mapping Type   | mapping of key-value pairs                    | `{'name':'Ada', 'subject':'computer science'}` |\n",
    "| none                  | `NoneType` | Null Object    | represents no value                           | `None`                                     |\n",
    "| function                  | `function` | Function   | Represents a function                           | `def add_one(x): return x+1`                                     |\n",
    "\n",
    "````{admonition} Exercise\n",
    "What type is this Python object?\n",
    "\n",
    "```python\n",
    "cities_to_temps = {\"Paris\": 32, \"London\": 22, \"Seville\": 36, \"Wellesley\": 29}\n",
    "```\n",
    "\n",
    "What type is the first key (hint: comma separated entries form key-value pairs)?\n",
    "````\n",
    "\n",
    "### Brackets\n",
    "\n",
    "You may notice that there are several kinds of brackets that appear in the code we've seen so far, including `[]`, `{}`, and `()`. These can play different roles depending on the context, but the most common uses are:\n",
    "\n",
    "- `[]` is used to denote a list, eg `['a', 'b']`, or to signify accessing a position using an index, eg `vector[0]` to get the first entry of a variable called vector.\n",
    "\n",
    "- `{}` is used to denote a set, eg `{'a', 'b'}`, or a dictionary (with pairs of terms), eg `{'first_letter': 'a', 'second_letter': 'b'}`.\n",
    "\n",
    "- `()` is used to denote a tuple, eg `('a', 'b')`, or the arguments to a function, eg `function(x)` where `x` is the input passed to the function, *or* to indicate the order operations are carried out.\n",
    "\n",
    "## Lists and slicing\n",
    "\n",
    "Lists are a really useful way to work with lots of data at once. They're defined with square brackets, with entries separated by commas. You can also construct them by appending entries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96c4b86b",
   "metadata": {},
   "outputs": [],
   "source": [
    "list_example.append(\"one more entry\")\n",
    "print(list_example)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8d4f6ed",
   "metadata": {},
   "source": [
    "And you can access earlier entries using an index, which begins at 0 and ends at one less than the length of the list (this is the convention in many programming languages). For instance, to print specific entries at the start, using `0`, and end, using `-1`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "138ac0e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(list_example[0])\n",
    "print(list_example[-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "920d19f4",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "How might you access the penultimate entry in a list object if you didn't know how many elements it had?\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6aea9157",
   "metadata": {},
   "source": [
    "As well as accessing positions in lists using indexing, you can use *slices* on lists. This uses the colon character, `:`, to stand in for 'from the beginning' or 'until the end' (when only appearing once). For instance, to print just the last two entries, we would use the index `-2:` to mean from the second-to-last onwards. Here are two distinct examples: getting the first three and last three entries to be successively printed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ef390263",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(list_example[:3])\n",
    "print(list_example[-3:])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c82b5c4a",
   "metadata": {},
   "source": [
    "Slicing can be even more elaborate than that because we can jump entries using a second colon. Here's a full example that begins at the second entry (remember the index starts at 0), runs up until the second-to-last entry (exclusive), and jumps every other entry inbetween (range just produces a list of integers from the value to one less than the last):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13584953",
   "metadata": {},
   "outputs": [],
   "source": [
    "list_of_numbers = list(range(1, 11))\n",
    "start = 1\n",
    "stop = -1\n",
    "step = 2\n",
    "print(list_of_numbers[start:stop:step])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "813e09bc",
   "metadata": {},
   "source": [
    "A handy trick is that you can print a reversed list entirely using double colons:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2bc8926",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(list_of_numbers[::-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39f80c9e",
   "metadata": {},
   "source": [
    "````{admonition} Exercise\n",
    "Slice the `list_example` from earlier to get only the first five entries.\n",
    "````"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6ff3ca4",
   "metadata": {},
   "source": [
    "What's amazing about lists is that they can hold any type, including other lists! Here's a valid example of a list that's got a lot going on:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "703fb0b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "wacky_list = [\n",
    "    3.1415,\n",
    "    16,\n",
    "    [\"five\", 4, 3],\n",
    "    (91, 93, 90),\n",
    "    \"Hello World!\",\n",
    "    True,\n",
    "    None,\n",
    "    {\"key\": \"value\", \"key2\": \"value2\"},\n",
    "]\n",
    "wacky_list"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "052994b7",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Can you identify the types of each of the entries in `wacky_list`?\n",
    "```\n",
    "\n",
    "## Operators\n",
    "\n",
    "All of the basic operators you see in mathematics are available to use: `+` for addition, `-` for subtraction, `*` for multiplication, `**` for powers, `/` for division, and `%` for modulo. These work as you'd expect on numbers. But these operators are sometimes defined for other built-in data types too. For instance, we can 'sum' strings (which really concatenates them):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b572cc37",
   "metadata": {},
   "outputs": [],
   "source": [
    "string_one = \"This is an example \"\n",
    "string_two = \"of string concatenation\"\n",
    "string_full = string_one + string_two\n",
    "print(string_full)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "701e70c5",
   "metadata": {},
   "source": [
    "It works for lists too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3778c87b",
   "metadata": {},
   "outputs": [],
   "source": [
    "list_one = [\"apples\", \"oranges\"]\n",
    "list_two = [\"pears\", \"satsumas\"]\n",
    "list_full = list_one + list_two\n",
    "print(list_full)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65d5d58a",
   "metadata": {},
   "source": [
    "Perhaps more surprisingly, you can multiply strings!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d609681",
   "metadata": {},
   "outputs": [],
   "source": [
    "string = \"apples, \"\n",
    "print(string * 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fb0e143",
   "metadata": {},
   "source": [
    "Below is a table of the basic arithmetic operations.\n",
    "\n",
    "| Operator |   Description    |\n",
    "| :------: | :--------------: |\n",
    "|   `+`    |     addition     |\n",
    "|   `-`    |   subtraction    |\n",
    "|   `*`    |  multiplication  |\n",
    "|   `/`    |     division     |\n",
    "|   `**`   |  exponentiation  |\n",
    "|   `//`   | integer division / floor division |\n",
    "|   `%`    |      modulo      |\n",
    "|   `@`    |     matrix multiplication |\n",
    "\n",
    "As well as the usual operators, Python supports *assignment operators*. An example of one is `x+=3`, which is equivalent to running `x = x + 3`. Pretty much all of the operators can be used in this way.\n",
    "\n",
    "```{admonition} Exercise\n",
    "Using Python operations only, what is \n",
    "\n",
    "$$\n",
    "\\frac{2^5}{7 \\cdot (4 - 2^3)}\n",
    "$$\n",
    "\n",
    "```\n",
    "\n",
    "## Strings\n",
    "\n",
    "In some ways, strings are treated a bit like lists, meaning you can access the individual characters via slicing and indexing. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4507b65c",
   "metadata": {},
   "outputs": [],
   "source": [
    "string = \"cheesecake\"\n",
    "print(string[-4:])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc8efb39",
   "metadata": {},
   "source": [
    "Both lists and strings will also allow you to use the `len` command to get their length:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e84654d",
   "metadata": {},
   "outputs": [],
   "source": [
    "string = \"cheesecake\"\n",
    "print(\"String has length:\")\n",
    "print(len(string))\n",
    "list_of_numbers = range(1, 20)\n",
    "print(\"List of numbers has length:\")\n",
    "print(len(list_of_numbers))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57fe793a",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "What is the `len` of a list created by `range(n)` where `n` could be any integer?\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aef23516",
   "metadata": {},
   "source": [
    "Strings have type `string` and can be defined by single or double quotes, eg `string = \"cheesecake\"` would have been equally valid above. It's best practice to use one convention and stick to it, and most people use double quotes for strings.\n",
    "\n",
    "There are various functions built into Python to help you work with strings that are particularly useful for cleaning messy data. For example, imagine you have a variable name like 'This Is /A Variable   '. (You may think this is implausibly bad; if only that were true...). Let's see if we can clean this up:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5fbef807",
   "metadata": {},
   "outputs": [],
   "source": [
    "string = \"This Is /A Variable   \"\n",
    "string = string.replace(\"/\", \"\").rstrip().lower()\n",
    "print(string)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f739a4f9",
   "metadata": {},
   "source": [
    "The steps above replace the character '/', strip out whitespace on the right hand-side of the string, and put everything in lower case. The brackets after the words signify that a function has been applied; we'll see more of functions later.\n",
    "\n",
    "```{admonition} Exercise\n",
    "Using string operations, strip the leading and trailing spaces, make upper case, and remove the underscores from the string `\"    this_is_a_better_variable_name   \"`.\n",
    "```\n",
    "\n",
    "**Changing Type to String**\n",
    "\n",
    "We'll look at this in more detail shortly, but while we're on strings, it seems useful to mention it now: you'll often want to output one type of data as another, and Python generally knows what you're trying to achieve if you, for example, `print` a boolean value. For numbers, there are more options and you can see a big list of advice on string formatting of all kinds of things [here](https://pyformat.info/). For now, let's just see a simple example of something called an f-string, a string that combines a number and a string (these begin with an `f` for formatting):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21661479",
   "metadata": {},
   "outputs": [],
   "source": [
    "value = 20\n",
    "sqrt_val = 20 ** 0.5\n",
    "print(f\"The square root of {value:d} is {sqrt_val:.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "85b869a5",
   "metadata": {},
   "source": [
    "The formatting command `:d` is an instruction to treat `value` like an integer, while `:.2f` is an instruction to print it like a float with 2 decimal places.\n",
    "\n",
    "```{note}\n",
    "f-strings are only available in Python 3.6+\n",
    "```\n",
    "\n",
    "```{admonition} Exercise\n",
    "Write a print command with the `sqrt_val` expressed to 3 decimal places.\n",
    "```\n",
    "\n",
    "## Booleans and conditions\n",
    "\n",
    "Some of the most important operations you will perform are with `True` and `False` values, also known as boolean data types. There are two types of operation that are associated with booleans: boolean operations, in which existing booleans are combined, and condition operations, which create a boolean when executed.\n",
    "\n",
    "Boolean operators that return booleans are as follows:\n",
    "\n",
    "| Operator | Description |\n",
    "| :---: | :--- |\n",
    "|`x and y`| are `x` and `y` both True? |\n",
    "|`x or y` | is at least one of `x` and `y` True? |\n",
    "| `not x` | is `x` False? | \n",
    "\n",
    "These behave as you'd expect: `True and False` evaluates to `False`, while `True or False` evaluates to `True`. There's also the `not` keyword. For example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "590cd75d",
   "metadata": {},
   "outputs": [],
   "source": [
    "not True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dcd0a01b",
   "metadata": {},
   "source": [
    "as you would expect.\n",
    "\n",
    "Conditions are expressions that evaluate as booleans. A simple example is `10 == 20`. The `==` is an operator that compares the objects on either side and returns `True` if they have the same *values*--though be careful using it with different data types.\n",
    "\n",
    "Here's a table of conditions that return booleans:\n",
    "\n",
    "| Operator  | Description                          |\n",
    "| :-------- | :----------------------------------- |\n",
    "| `x == y ` | is `x` equal to `y`?                 |\n",
    "| `x != y`  | is `x` not equal to `y`?             |\n",
    "| `x > y`   | is `x` greater than `y`?             |\n",
    "| `x >= y`  | is `x` greater than or equal to `y`? |\n",
    "| `x < y`   | is `x` less than `y`?                |\n",
    "| `x <= y`  | is `x` less than or equal to `y`?    |\n",
    "| `x is y`  | is `x` the same object as `y`?       |\n",
    "\n",
    "\n",
    "As you can see from the table, the opposite of `==` is `!=`, which you can read as 'not equal to the value of'. Here's an example of `==`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "51622575",
   "metadata": {},
   "outputs": [],
   "source": [
    "boolean_condition = 10 == 20\n",
    "print(boolean_condition)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4367753",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "What does `not (not True)` evaluate to?\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91dd45b6",
   "metadata": {},
   "source": [
    "The real power of conditions comes when we start to use them in more complex examples. Some of the keywords that evaluate conditions are `if`, `else`, `and`, `or`, `in`, `not`, and `is`. Here's an example showing how some of these conditional keywords work:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c550daa",
   "metadata": {},
   "outputs": [],
   "source": [
    "name = \"Ada\"\n",
    "score = 99\n",
    "\n",
    "if name == \"Ada\" and score > 90:\n",
    "    print(\"Ada, you achieved a high score.\")\n",
    "\n",
    "if name == \"Smith\" or score > 90:\n",
    "    print(\"You could be called Smith or have a high score\")\n",
    "\n",
    "if name != \"Smith\" and score > 90:\n",
    "    print(\"You are not called Smith and you have a high score\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e01a3f9",
   "metadata": {},
   "source": [
    "All three of these conditions evaluate as True, and so all three messages get printed. Given that `==` and `!=` test for equality and not equal, respectively, you may be wondering what the keywords `is` and `not` are for. Remember that everything in Python is an object, and that values can be assigned to objects. `==` and `!=` compare *values*, while `is` and `not` compare *objects*. For example,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7420e1c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "name_list = [\"Ada\", \"Adam\"]\n",
    "name_list_two = [\"Ada\", \"Adam\"]\n",
    "\n",
    "# Compare values\n",
    "print(name_list == name_list_two)\n",
    "\n",
    "# Compare objects\n",
    "print(name_list is name_list_two)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a78e664",
   "metadata": {},
   "source": [
    "Note that code with lots of branching if statements is not very helpful to you or to anyone else who reads your code. Some automatic code checkers will pick this up and tell you that your code is too complex. Almost all of the time, there's a way to rewrite your code without lots of branching logic that will be better and clearer than having many nested `if` statements."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56b4f2ef",
   "metadata": {},
   "source": [
    "One of the most useful conditional keywords is `in`. This one must pop up ten times a day in most coders' lives because it can pick out a variable or make sure something is where it's supposed to be."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39caa7be",
   "metadata": {},
   "outputs": [],
   "source": [
    "name_list = [\"Lovelace\", \"Smith\", \"Hopper\", \"Babbage\"]\n",
    "\n",
    "print(\"Lovelace\" in name_list)\n",
    "\n",
    "print(\"Bob\" in name_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e044d14a",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Check if \"a\" is in the string \"Walloping weasels\" using `in`. Is \"a\" `in` \"Anodyne\"?\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19f0782e",
   "metadata": {},
   "source": [
    "The opposite is `not in`.\n",
    "\n",
    "Finally, one conditional construct you're bound to use at *some* point, is the `if`...`else` structure:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "95794e71",
   "metadata": {},
   "outputs": [],
   "source": [
    "score = 98\n",
    "\n",
    "if score == 100:\n",
    "    print(\"Top marks!\")\n",
    "elif score > 90 and score < 100:\n",
    "    print(\"High score!\")\n",
    "elif score > 10 and score <= 90:\n",
    "    pass\n",
    "else:\n",
    "    print(\"Better luck next time.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94a359e7",
   "metadata": {},
   "source": [
    "Note that this does nothing if the score is between 11 and 90, and prints a message otherwise.\n",
    "\n",
    "```{admonition} Exercise\n",
    "Create a new `if` ... `elif` ... `else` statement that prints \"well done\" if a score is over 90, \"good\" if between 40 and 90, and \"bad luck\" otherwise.\n",
    "```\n",
    "\n",
    "One nice feature of Python is that you can make multiple boolean comparisons in a single line."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd1cd061",
   "metadata": {},
   "outputs": [],
   "source": [
    "a, b = 3, 6\n",
    "\n",
    "1 < a < b < 20"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb52a4f7",
   "metadata": {},
   "source": [
    "## Casting variables\n",
    "\n",
    "Sometimes we need to explicitly cast a value from one type to another. We can do this using functions like `str()`, `int()`, and `float()`. If you try these, Python will do its best to interpret the input and convert it to the output type you'd like and, if they can't, the code will throw a great big error.\n",
    "\n",
    "Here's an example of casting a `float` as an `int`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ea84ad6",
   "metadata": {},
   "outputs": [],
   "source": [
    "orig_number = 4.39898498\n",
    "type(orig_number)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72354de6",
   "metadata": {},
   "source": [
    "Now we cast it to an int:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "328a25fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "mod_number = int(orig_number)\n",
    "mod_number"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c65a1047",
   "metadata": {},
   "source": [
    "which looks like it became an integer, but we can double check that:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "95bb98d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "type(mod_number)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "133a790a",
   "metadata": {},
   "source": [
    "## Tuples and (im)mutability\n",
    "\n",
    "A tuple is an object that is defined by parentheses and entries that are separated by commas, for example `(15, 20, 32)`. (They are of type `tuple`.) As such, they have a lot in common with lists-but there's a big and important difference.\n",
    "\n",
    "Tuples are immutable, while lists are mutable. This means that, once defined, we can always modify a list using slicing and indexing, e.g. to change the first entry of a list called `listy` we would use `listy[0] = 5`. But trying to do this with a tuple will result in an error.\n",
    "\n",
    "*Immutable* objects, such as tuples, can't have their elements changed, appended, extended, or removed. Lists can do all of these things. Tuples aren't the only immutable objects in Python; strings are immutable too.\n",
    "\n",
    "You may wonder why both are needed given lists seem to provide a superset of functionality: sometimes in coding, lack of flexibility is a *good* thing because it restricts the number of ways a process can go awry. I dare say there are other reasons too, but you don't need to worry about them and using lists is a good default most of the time.\n",
    "\n",
    "````{admonition} Exercise\n",
    "Are the entries in dictionaries mutable or immutable? To help you answer this, a simple dictionary can be defined as\n",
    "\n",
    "```python\n",
    "my_dict = {\"a\": 1, \"b\": 2}\n",
    "```\n",
    "\n",
    "and the values are accessed by `dictionary[key]`, eg `my_dict[\"a\"]` for this example.\n",
    "````\n",
    "\n",
    "## Indentation\n",
    "\n",
    "You'll have seen that certain parts of the code examples are indented. Code that is part of a function, a conditional clause, or loop is indented. This isn't a code style choice, it's actually what tells the language that some code is to be executed as part of, say, a loop and not to executed after the loop is finished.\n",
    "\n",
    "Here's a basic example of indentation as part of an `if` loop. The `print` statement that is indented only executes if the condition evaluates to true."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1d000673",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = 10\n",
    "\n",
    "if x > 2:\n",
    "    print(\"x is greater than 2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8b8f645",
   "metadata": {},
   "source": [
    "```{tip}\n",
    "The VS Code extension *indent-rainbow* colours different levels of indentation differently for ease of reading.\n",
    "```\n",
    "\n",
    "When functions, conditional clauses, or loops are combined together, they each cause an *increase* in the level of indentation. Here's a double indent."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bf319227",
   "metadata": {},
   "outputs": [],
   "source": [
    "if x > 2:\n",
    "    print(\"outer conditional cause\")\n",
    "    for i in range(4):\n",
    "        print(\"inner loop\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19e65736",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "The standard practice for indentation is that each sub-statement should be indented by 4 spaces. It can be hard to keep track of these but, as usual, Visual Studio Code has you covered. Go to Settings (the cog in the bottom left-hand corner, then click Settings) and type 'Whitespace' into the search bar. Under 'Editor: Render Whitespace', select 'boundary'. This will show any whitespace that is more than one character long using faint grey dots. Each level of indentation in your Python code should now begin with four grey dots showing that it consists of four spaces.\n",
    "\n",
    "```{tip}\n",
    "Rendering whitespace using Visual Studio Code's settings makes it easier to navigate different levels of indentation.\n",
    "```\n",
    "\n",
    "```{admonition} Exercise\n",
    "Try writing a code snippet that reaches the triple level of indentation. \n",
    "```\n",
    "\n",
    "## Loops and list comprehensions\n",
    "\n",
    "A loop is a way of executing a similar piece of code over and over in a similar way. The most useful loops are `for` loops and list comprehensions.\n",
    "\n",
    "A `for` loop does something *for* the time that the condition is satisfied. For example,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a2bbd41c",
   "metadata": {},
   "outputs": [],
   "source": [
    "name_list = [\"Lovelace\", \"Smith\", \"Pigou\", \"Babbage\"]\n",
    "\n",
    "for name in name_list:\n",
    "    print(name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb31bf67",
   "metadata": {},
   "source": [
    "prints out a name until all names have been printed out. Note the colon after the statement and before the indent.\n",
    "\n",
    "As long as your object is an iterable (ie you can iterate over it), then it can be used in this way in a for loop. The most common examples are lists and tuples, but you can also iterate over strings (in which case each character is selected in turn). One gotcha to be aware of is if you iterate over a string, say \"hello\", instead of iterating over a *list (or tuple) of strings*, eg `[\"hello\"]`. In the latter case, you get:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "835ebda7",
   "metadata": {},
   "outputs": [],
   "source": [
    "for entry in [\"hello\"]:\n",
    "    print(entry)\n",
    "    print(\"---end entry---\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a6dec3e",
   "metadata": {},
   "source": [
    "While in the former you get something quite different and typically not all that useful:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a19ac2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "for entry in \"hello\":\n",
    "    print(entry)\n",
    "    print(\"---end entry---\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a080093",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Write a for loop that prints out \"coding for economists\" so that each word is printed in a successive iteration.\n",
    "```\n",
    "\n",
    "A useful trick with for loops is the `enumerate` keyword, which runs through an index that keeps track of the place of items in a list:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "239e133f",
   "metadata": {},
   "outputs": [],
   "source": [
    "name_list = [\"Lovelace\", \"Smith\", \"Hopper\", \"Babbage\"]\n",
    "\n",
    "for i, name in enumerate(name_list):\n",
    "    print(f\"The name in position {i} is {name}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3547896",
   "metadata": {},
   "source": [
    "Remember, Python indexes from 0 so the first entry of `i` will be zero. But, if you'd like to index from a different number, you can:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b66c5c53",
   "metadata": {},
   "outputs": [],
   "source": [
    "for i, name in enumerate(name_list, start=1):\n",
    "    print(f\"The name in position {i} is {name}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8c12f6d",
   "metadata": {},
   "source": [
    "Another useful pattern when doing for loops with dictionaries is iteration over key, value pairs. We'll get to learn more about dictionaries very shortly, but for now what's important is that they map a key to a value, for example \"apple\" might map to \"fruit\". Let's take our example from earlier that mapped cities to temperatures. If we wanted to iterate over *both* keys and values, we can write a for loop like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "010239fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "cities_to_temps = {\"Paris\": 28, \"London\": 22, \"Seville\": 36, \"Wellesley\": 29}\n",
    "\n",
    "for key, value in cities_to_temps.items():\n",
    "    print(f\"In {key}, the temperature is {value} degrees C today.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42aef792",
   "metadata": {},
   "source": [
    "Note that we added `.items()` to the end of the dictionary. And note that we didn't *have* to call the key `key`, or the value `value`: these are set by their position. But part of best practice in writing code is that *there should be no surprises*, and writing key, value makes it really clear that you're using values from a dictionary.\n",
    "\n",
    "```{admonition} Exercise\n",
    "Write a dictionary that maps four cities you know into their respective countries and print the results using the `key, value` iteration trick.\n",
    "```\n",
    "\n",
    "Another useful type of for loop is provided by the `zip` function. You can think of the `zip` function as being like a zipper, bringing elements from two different iterators together in turn. Here's an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ea3efc5",
   "metadata": {},
   "outputs": [],
   "source": [
    "first_names = [\"Ada\", \"Adam\", \"Grace\", \"Charles\"]\n",
    "last_names = [\"Lovelace\", \"Smith\", \"Hopper\", \"Babbage\"]\n",
    "\n",
    "for forename, surname in zip(first_names, last_names):\n",
    "    print(f\"{forename} {surname}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c07737f3",
   "metadata": {},
   "source": [
    "The zip function is super useful in practice.\n",
    "\n",
    "```{admonition} Exercise\n",
    "Zip together the first names from above with this jumbled list of surnames: `['Babbage', 'Hopper', 'Smith', 'Lovelace']`.\n",
    "\n",
    "(Hint: you have seen a trick to help re-arrange lists earlier on in the Chapter.)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba79c3dc",
   "metadata": {},
   "source": [
    "**List (and Other) Comprehensions**\n",
    "\n",
    "There's a second way to do loops in Python and, in most but [not all](https://towardsdatascience.com/list-comprehensions-vs-for-loops-it-is-not-what-you-think-34071d4d8207) [cases](https://stackoverflow.com/questions/22108488/are-list-comprehensions-and-functional-functions-faster-than-for-loops), they run faster. More importantly, and *this* is the reason it's good practice to use them where possible, they are very readable. They are called *list comprehensions*.\n",
    "\n",
    "List comprehensions can combine what a `for` loop and (if needed) what a `condition` do in a single line of code. First, let's look at a `for` loop that adds one to each value done as a list comprehension (NB: in practice, we would use super-fast **numpy** arrays for this kind of operation):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7efed381",
   "metadata": {},
   "outputs": [],
   "source": [
    "num_list = range(50, 60)\n",
    "[1 + num for num in num_list]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aaca4b6b",
   "metadata": {},
   "source": [
    "The general pattern is a bit similar to with the `for` loop but there are some differences. There's no colon, and no indenting. The syntax is \"do something with `x`\" then `for x in iterable`. Finally, the expression is wrapped in a `[` and `]` to make the output a list.\n",
    "\n",
    "Note that lists are not the only wrapping you can provide to this kind of structure. A `(` and `)` to make it a generator (don't worry about what this is for now), a `{` and `}` to make it a set (an object that only contains unique values), or it's possible to create a dictionary from a comprehension too! List comprehensions are the most common, so if you only remember one kind, remember them.\n",
    "\n",
    "```{admonition} Exercise\n",
    "Create a list comprehension that multiplies numbers in the range from 1 to 10 by 5.\n",
    "\n",
    "Did you get the range right?\n",
    "```\n",
    "\n",
    "Let's now see how to include a condition within a list comprehension. Say we had a list of numbers and wanted to filter it according to whether the numbers divided by 3 or not using the modulo operator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "722fda21",
   "metadata": {},
   "outputs": [],
   "source": [
    "number_list = range(1, 40)\n",
    "divide_list = [x for x in number_list if x % 3 == 0]\n",
    "print(divide_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "326a9c20",
   "metadata": {},
   "source": [
    "The syntax here is do something to `x` for `x` in something if `x` satisfies some condition.\n",
    "\n",
    "Here's another example that picks out only the names that include 'Smith' in them:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b6e80d6b",
   "metadata": {},
   "outputs": [],
   "source": [
    "names_list = [\"Joe Bloggs\", \"Adam Smith\", \"Sandra Noone\", \"leonara smith\"]\n",
    "smith_list = [x for x in names_list if \"smith\" in x.lower()]\n",
    "print(smith_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f67de061",
   "metadata": {},
   "source": [
    "Note how we used 'smith' rather than 'Smith' and then used `lower()` to ensure we matched names regardless of the case they are written in.\n",
    "\n",
    "We can even do a whole `if` ... `else` construct *inside* a list comprehension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f348bfb6",
   "metadata": {},
   "outputs": [],
   "source": [
    "names_list = [\"Joe Bloggs\", \"Adam Smith\", \"Sandra Noone\", \"leonara smith\"]\n",
    "smith_list = [x if \"smith\" in x.lower() else \"Not Smith!\" for x in names_list]\n",
    "print(smith_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72548d09",
   "metadata": {},
   "source": [
    "Many of the constructs we've seen can be combined. For instance, there is no reason why we can't have a nested or repeated list comprehension using `zip`, and, perhaps more surprisingly, sometimes these are useful!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74e4fcc7",
   "metadata": {},
   "outputs": [],
   "source": [
    "first_names = [\"Ada\", \"Adam\", \"Grace\", \"Charles\"]\n",
    "last_names = [\"Lovelace\", \"Smith\", \"Hopper\", \"Babbage\"]\n",
    "names_list = [x + \" \" + y for x, y in zip(first_names, last_names)]\n",
    "print(names_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ec0597d",
   "metadata": {},
   "source": [
    "An even more extreme use of list comprehensions can deliver nested structures:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c82cf1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "first_names = [\"Ada\", \"Adam\"]\n",
    "last_names = [\"Lovelace\", \"Smith\"]\n",
    "names_list = [[x + \" \" + y for x in first_names] for y in last_names]\n",
    "print(names_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b83ece8",
   "metadata": {},
   "source": [
    "This gives a nested structure that (in this case) iterates over `first_names` first, and then `last_names`. (Note that this object is a list of lists of strings!)\n",
    "\n",
    "Let's see a dictionary comprehension now. These look a bit similar to set comprehensions because they use `{` and `}` at either end but they are different because they come with a colon separating the keys from the values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "acef16ae",
   "metadata": {},
   "outputs": [],
   "source": [
    "{key: value for key, value in zip(first_names, last_names)}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d18be80b",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Create a nested list comprehension that results in a list of lists of strings equal to `[['a0', 'b0', 'c0'], ['a1', 'b1', 'c1'], ['a2', 'b2', 'c2']]` (ie a combination of the first three integers and letters of the alphabet). You may find that you need to convert numbers to strings using `str(x)` to do this.\n",
    "```\n",
    "\n",
    "If you'd like to learn more about list comprehensions, check out these [short video tutorials](https://calmcode.io/comprehensions/introduction.html).\n",
    "\n",
    "## While loops\n",
    "\n",
    "`while` loops continue to execute code until their conditional expression evaluates to `False`. (Of course, if it evaluates to `True` forever, your code will just continue to execute...)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e47ba02",
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 10\n",
    "while n > 0:\n",
    "    print(n)\n",
    "    n -= 1\n",
    "\n",
    "print(\"execution complete\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0131cc60",
   "metadata": {},
   "source": [
    "NB: in case you're wondering what `-=` does, it's a compound assignment that sets the left-hand side equal to the left-hand side minus the right-hand side.\n",
    "\n",
    "You can use the keyword `break` to break out of a while loop, for example if it's reached a certain number of iterations without converging.\n",
    "\n",
    "```{admonition} Exercise\n",
    "Making use of `import string` and then `string.ascii_lowercase` to get the characters in the alphabet, write a while loop that iterates backwards through the alphabet (starting at \"z\") before printing \"execution complete\".\n",
    "```\n",
    "\n",
    "## Dictionaries\n",
    "\n",
    "Another built-in Python type that is enormously useful is the *dictionary*. This provides a mapping one set of variables to another (either one-to-one or many-to-one). Let's see an example of defining a dictionary and using it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6ac3fab1",
   "metadata": {},
   "outputs": [],
   "source": [
    "fruit_dict = {\n",
    "    \"Jazz\": \"Apple\",\n",
    "    \"Owari\": \"Satsuma\",\n",
    "    \"Seto\": \"Satsuma\",\n",
    "    \"Pink Lady\": \"Apple\",\n",
    "}\n",
    "\n",
    "# Add an entry\n",
    "fruit_dict.update({\"Cox\": \"Apple\"})\n",
    "\n",
    "variety_list = [\"Jazz\", \"Jazz\", \"Seto\", \"Cox\"]\n",
    "\n",
    "fruit_list = [fruit_dict[x] for x in variety_list]\n",
    "print(fruit_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a959784",
   "metadata": {},
   "source": [
    "From an input list of varieties, we get an output list of their associated fruits. Another good trick to know with dictionaries is that you can iterate through their keys and values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3cfa514c",
   "metadata": {},
   "outputs": [],
   "source": [
    "for key, value in fruit_dict.items():\n",
    "    print(key + \" maps into \" + value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c245317",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Update the cities dictionary from earlier in this Chapter with another two cities and then iterate through them printing each mapping using `.items()` as above.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bfa1201",
   "metadata": {},
   "source": [
    "## Running on empty\n",
    "\n",
    "Being able to create empty containers is sometimes useful. The commands to create empty lists, tuples, dictionaries, and sets are `lst = []`, `tup=()`, `dic={}`, and `st = set()` respectively.\n",
    "\n",
    "```{admonition} Exercise\n",
    "What is the type of an empty list?\n",
    "```\n",
    "\n",
    "## Functions\n",
    "\n",
    "If you're an economist, you hardly need to be told you what a function is. In coding, it's much the same as in mathematics: a function has inputs, it performs its function, and it returns any outputs. Functions begin with a `def` keyword for 'define a function'. It then has a name, followed by brackets, `()`, which may contain *function arguments* and *function keyword arguments*. This is followed by a colon. The body of the function is then indented relative to the left-most text. Function arguments are defined in brackets following the name, with different inputs separated by commas. Any outputs are given with the `return` keyword, again with different variables separated by commas.\n",
    "\n",
    "```{admonition} Arguments and keyword arguments\n",
    ":class: tip\n",
    "\n",
    "*arguments* are the variables that functions *always* need, so `a` and `b` in `def add(a, b): return a + b`. The function won't work without them! Function arguments are sometimes referred to as *args*.\n",
    "\n",
    "*Keyword arguments* are the variables that are optional for functions, so `c` in `def add(a, b, c=5): return a + b - c`. If you do not provide a value for `c` when calling the function, it will automatically revert to `c=5`. Keyword arguments are sometimes referred to as *kwargs*.\n",
    "```\n",
    "\n",
    "Let's see a very simple example of a function with a single *argument* (or arg):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0450ad6c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def welcome_message(name):\n",
    "    return f\"Hello {name}, and welcome!\"\n",
    "\n",
    "\n",
    "# Without indentation, this code is not part of function\n",
    "name = \"Ada\"\n",
    "output_string = welcome_message(name)\n",
    "print(output_string)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08c06c6a",
   "metadata": {},
   "source": [
    "One powerful feature of functions is that we can define defaults for the input arguments. These are called *keyword arguments* (or kwargs). Let's see that in action by defining a default value for `name`, along with multiple outputs--a hello message and a score."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd49bee5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def score_message(score, name=\"student\"):\n",
    "    \"\"\"This is a doc-string, a string describing a function.\n",
    "    Args:\n",
    "        score (float): Raw score\n",
    "        name (str): Name of student\n",
    "    Returns:\n",
    "        str: A hello message.\n",
    "        float: A normalised score.\n",
    "    \"\"\"\n",
    "    norm_score = (score - 50) / 10\n",
    "    return f\"Hello {name}\", norm_score\n",
    "\n",
    "\n",
    "# Without indentation, this code is not part of function\n",
    "name = \"Ada\"\n",
    "score = 98\n",
    "# No name entered\n",
    "print(score_message(score))\n",
    "# Name entered\n",
    "print(score_message(score, name=name))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c4c6cdf",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "What is the return type of a function with multiple return values separated by commas following the `return` statement?\n",
    "```\n",
    "\n",
    "In that last example, you'll notice that we added some text to the function. This is a doc-string, or documentation string. It's there to help users (and, most likely, future you) to understand what the function does. Let's see how this works in action by calling `help` on the `score_message` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50dfff24",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(score_message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e466fcd",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Write a function that returns a high five unicode character if the input is equal to \"coding for economists\" and a sad face, \":-/\" otherwise.\n",
    "\n",
    "Add a second argument that takes a default argument of an empty string but, if used, is added (concatenated) to the return message. Use it to create the return output, \":-/ here is my message.\"\n",
    "\n",
    "Write a doc-string for your function and call `help` on it.\n",
    "```\n",
    "\n",
    "To learn more about args and kwargs, check out these [short video tutorials](https://calmcode.io/args-kwargs/introduction.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6735f101",
   "metadata": {},
   "source": [
    "## Scope\n",
    "\n",
    "Scope refers to what parts of your code can see what other parts. There are three different scopes to bear in mind: local, global, and non-local.\n",
    "\n",
    "**Local**\n",
    "\n",
    "If you define a variable inside a function, the rest of your code won't be able to 'see' it or use it. For example, here's a function that creates a variable and then an example of calling that variable:\n",
    "\n",
    "```python\n",
    "def var_func():\n",
    "    str_variable = 'Hello World!'\n",
    "\n",
    "var_func()\n",
    "print(str_variable)\n",
    "```\n",
    "\n",
    "This would raise an error, because as far as your general code is concerned `str_variable` doesn't exist outside of the function. This is an example of a *local* variable, one that only exists within a function.\n",
    "\n",
    "\n",
    "If you want to create variables inside a function and have them persist, you need to explicitly pass them out using, for example `return str_variable` like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8725b986",
   "metadata": {},
   "outputs": [],
   "source": [
    "def var_func():\n",
    "    str_variable = \"Hello World!\"\n",
    "    return str_variable\n",
    "\n",
    "\n",
    "returned_var = var_func()\n",
    "print(returned_var)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddd6f63e",
   "metadata": {},
   "source": [
    "**Global**\n",
    "\n",
    "A variable declared outside of a function is known as a global variable because it is accessible everywhere:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0a4fe15",
   "metadata": {},
   "outputs": [],
   "source": [
    "y = \"I'm a global variable\"\n",
    "\n",
    "def print_y():\n",
    "    print(\"y is inside a function:\", y)\n",
    "\n",
    "\n",
    "print_y()\n",
    "print(\"y is outside a function:\", y)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "789dd36a",
   "metadata": {},
   "source": [
    "**nonlocal**\n",
    "\n",
    "```{warning}\n",
    "If you are just learning to code you should feel free to skip this section.\n",
    "```\n",
    "\n",
    "Finally, non-local variables are used in nested functions as a means to say 'hey, this variable is not just local to this nested function, it exists outside it too'. Here's an example that prints \"world\" because we tell the inner function to use the same `x` as the outer function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d78cc72",
   "metadata": {},
   "outputs": [],
   "source": [
    "def outer_function():\n",
    "  x = \"hello\"\n",
    "  def nested_function():\n",
    "    nonlocal x\n",
    "    x = \"world\"\n",
    "  nested_function()\n",
    "  return x\n",
    "\n",
    "print(outer_function())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d95d5ec7",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Re-write the above function without the `nonlocal` keyword. What does it print?\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0154c032",
   "metadata": {},
   "source": [
    "## Using packages and modules\n",
    "\n",
    "We already saw how to install packages in the previous chapter. If you forgot, look back at the Installing Packages section of {ref}`code-preliminaries` now. In short, packages are installed using the command line or, on Windows, the Anaconda prompt. With either of these open, first try typing `conda install packagename` and hitting enter. If the package isn't available on conda, you can use `pip install packagename` as a back-up.\n",
    "\n",
    "````{note} Advanced: install a package in a specific environment\n",
    "We haven't seen isolated Python environments yet, but if you want to install packages into a dedicated environment using `conda`, you'll need to run `conda activate environmentname` before using the package install command(s). If the command works, you should see `(environmentname)` rather than `(base)` at the start of the command prompt. Using bash, the steps would be:\n",
    "\n",
    "```bash\n",
    "(base) your-computer:your-folder your-username$ conda activate environmentname\n",
    "(environmentname) your-computer:your-folder your-username$ conda install packagename\n",
    "```\n",
    "\n",
    "````\n",
    "\n",
    "What about using a package that you've installed? That's what we'll look at now.\n",
    "\n",
    "Let's see an example of using the powerful numerical library **numpy**. There are different ways to import packages to use within a script or notebook; you can import the entire package in one go or just import the functions you need (if you know their names). When an entire package is imported, you can give it any name you like and the convention for **numpy** is to import it as the shortened 'np'. All of the functions and methods of the package can be accessed by typing `np` followed by `.` and then typing the function name. This convention of importing packages with a given name makes your code easier to read, because you know exactly which package is doing the heavy lifting, and avoids any conflicts when functions from different packages have the same name.\n",
    "\n",
    "As well as demonstrating importing the whole package for **numpy**, the example below shows importing just one specific function, for matrix inversion and called `inv`, from within **numpy**. Note that because `inv` was imported separately it can be used without an `np` prefix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f86893cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from numpy.linalg import inv\n",
    "\n",
    "matrix = np.array([[4.0, 2.0, 4.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])\n",
    "\n",
    "print(\"Matrix:\")\n",
    "print(matrix)\n",
    "\n",
    "inv_mat = inv(matrix)\n",
    "print(\"Inverse:\")\n",
    "print(inv_mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb7ac525",
   "metadata": {},
   "source": [
    "We could have imported all of **numpy** using `from numpy import *` but this is considered bad practice as it fills our 'namespace' with function names that might clash with other packages and it's less easy to parse which package's function is doing what. R also relies heavily on imported libraries but uses a different convention for namespaces: in that case, everything is imported but the *order* of package imports matters for what functions are default (and clashing function names are over-written).\n",
    "\n",
    "```{note}\n",
    "If you want to check what packages you have installed in your Python environment, run `conda list` on the command line.\n",
    "```\n",
    "\n",
    "There are times when you might forget what a function you have imported does! In Visual Studio Code, you can just hover your cursor over the name of the function and a box will come up that tells you everything you need to know about it. This box is auto-generated by the doc-strings we saw earlier in this Chapter (another reason why it's good practice to write doc-strings!).\n",
    "\n",
    "An alternative way to see what a function does is to use a wonderful package called [**rich**](https://github.com/willmcgugan/rich) that does many things including providing an `inspect` function. You will need to use `pip` to install rich by running `pip install rich` on the command line. Here's an example of using **rich**'s inpsect method on the `inv` function we imported above (`methods=True` reports all of the functionality of `inv`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4fd14fd9",
   "metadata": {},
   "outputs": [],
   "source": [
    "from rich import inspect\n",
    "\n",
    "inspect(inv, help=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "653880d8",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Write a code block that imports the **numpy** function `numpy.linalg.det` as `det`. Run `inspect` on it. Find the determinant of `[[4, 3], [1, 7]]`.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38d912c5",
   "metadata": {},
   "source": [
    "### Modules\n",
    "\n",
    "Sometimes, you will want to call in some code from a different script that you wrote rather than a different package. Imagine you have several code scripts (a, b, and c), all of which use the same underlying function that *you* have written. A central tenet of good coding is that you *do not repeat yourself*. Therefore, a bad solution to this problem would be to copy and paste the same code into all three of the scripts. A *good* solution is to write the code that's need just once in a separate 'utility' script and have the other scripts import that one function. This also adheres to another important programming principle: that of writing modular code.\n",
    "\n",
    "This schematic shows the kind of situation we're talking about:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0efeaee9",
   "metadata": {},
   "outputs": [],
   "source": [
    "import networkx as nx\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "graph = nx.DiGraph()\n",
    "graph.add_edges_from(\n",
    "    [\n",
    "        (\"Utility script\", \"code file a\"),\n",
    "        (\"Utility script\", \"code file b\"),\n",
    "        (\"code file a\", \"code file c\"),\n",
    "        (\"code file b\", \"code file c\"),\n",
    "        (\"Utility script\", \"code file c\"),\n",
    "    ]\n",
    ")\n",
    "colour_node = \"#AFCBFF\"\n",
    "fixed_pos = nx.spring_layout(graph, seed=100)\n",
    "nx.draw(graph, pos=fixed_pos, with_labels=True, node_size=5000, node_color=colour_node)\n",
    "plt.xlim(-1.3, 1.3)\n",
    "plt.ylim(-1.3, 1.3)\n",
    "plt.show();"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5df7665d",
   "metadata": {},
   "source": [
    "How can we give code files a, b, and c access to the functions etc in the \"Utility script\"? We would define a file 'utilities.py' that had the following function in that we would like to use in the other code files:\n",
    "\n",
    "```python\n",
    "# Contents of utilities.py file\n",
    "def really_useful_func(number):\n",
    "    return number*10\n",
    "```\n",
    "\n",
    "Then, in 'code_script_a.py', we would write:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2e3422c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import utilities as utils\n",
    "\n",
    "print(utils.really_useful_func(20))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb775643",
   "metadata": {},
   "source": [
    "An alternative is to *just* import the function we want, with the name we want:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb14c455",
   "metadata": {},
   "outputs": [],
   "source": [
    "from utilities import really_useful_func as ru_fn\n",
    "\n",
    "print(ru_fn(30))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15365d79",
   "metadata": {},
   "source": [
    "Another important example is the case where you want to run 'utilities.py' as a script, but still want to borrow functions from it to run in other scripts. There's a way to do this. Let's change utilities.py to\n",
    "\n",
    "```python\n",
    "# Contents of utilities.py file\n",
    "def really_useful_func(number):\n",
    "    return number*10\n",
    "\n",
    "\n",
    "def default_func():\n",
    "    print('Script has run')\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    default_func()\n",
    "```\n",
    "\n",
    "What this says is that if we call 'utilities.py' from the command line, eg\n",
    "\n",
    "```bash\n",
    "python utilities.py\n",
    "```\n",
    "\n",
    "It will return `Script has run` because, by executing the script alone, we are asking for anything in the `main` block defined at the end of the file to be run. But we can still import anything from utilities into other scripts as before--and in that case it is not the main script, but an import, and so the `main` block will *not* be executed.\n",
    "\n",
    "You can important several functions at once from a module (aka another script file) like this:\n",
    "\n",
    "```python\n",
    "from utilities import really_useful_func, default_func\n",
    "```\n",
    "\n",
    "```{admonition} Exercise\n",
    "Write your own `utilities.py` that has a `super_useful_func` that accepts a number and returns the number divided by 10. In another script, `main.py`, try a) importing all of utilities and running `super_useful_func` on a number and, b), importing just `super_useful_func` from utilities and running it on a number.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29d972f4",
   "metadata": {},
   "source": [
    "## Time\n",
    "\n",
    "Let's do a quick dive into how to deal with dates and times. This is only going to scratch the surface, but should give a sense of what's possible.\n",
    "\n",
    "The built-in library that deals with datetimes is called `datetime`. Let's import it and ask it to give us a very precise account of the datetime (when the code is executed):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ac0f26f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "\n",
    "now = datetime.now()\n",
    "print(now)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ba035ad",
   "metadata": {},
   "source": [
    "You can pick out bits of the datetime that you need:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f665624d",
   "metadata": {},
   "outputs": [],
   "source": [
    "day = now.day\n",
    "month = now.month\n",
    "year = now.year\n",
    "hour = now.hour\n",
    "minute = now.minute\n",
    "print(f\"{year}/{month}/{day}, {hour}:{minute}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "af5c69b2",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Using an f-string, add seconds to the date and time string above.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "066b21c2",
   "metadata": {},
   "source": [
    "To add or subtract time to a datetime, use `timedelta`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55a71293",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import timedelta\n",
    "\n",
    "new_time = now + timedelta(days=365, hours=5)\n",
    "print(new_time)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64b0600a",
   "metadata": {},
   "source": [
    "To take the difference of two dates:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b6f54bf0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import date\n",
    "\n",
    "new_year = date(year=2022, month=1, day=1)\n",
    "time_till_ny = new_year - date.today()\n",
    "print(f\"{time_till_ny.days} days until New Year\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45bfe197",
   "metadata": {},
   "source": [
    "Note that date and datetime are two different types of objects-a datetime includes information on the date and time, whereas a date does not.\n",
    "\n",
    "## Reading and writing files\n",
    "\n",
    "Although most applications in economics will probably use the **pandas** package to read and write tabular data, it's sometimes useful to know how to read and write arbitrary files using the built-in Python libraries too. To open a file\n",
    "\n",
    "```python\n",
    "open('filename', mode)\n",
    "```\n",
    "\n",
    "where `mode` could be `r` for read, `a` for append, `w` for write, and `x` to create a file. Create a file called `text_example.txt` and write a single line in it, 'hello world'. To open the file and print the text, use:\n",
    "\n",
    "```python\n",
    "with open('text_example.txt') as f:\n",
    "    text_in = f.read()\n",
    "\n",
    "print(text_in)\n",
    "```\n",
    "\n",
    "```python\n",
    "'hello world!\\n'\n",
    "```\n",
    "\n",
    "`\\n` is the new line character. Now let's try adding a line to the file:\n",
    "\n",
    "```python\n",
    "with open('text_example.txt', 'a') as f:\n",
    "    f.write('this is another line\\n')\n",
    "```\n",
    "\n",
    "Writing and reading files using the `with` command is a quick and convenient shorthand for the less concise open, action, close pattern. For example, the above example can also be written as:\n",
    "\n",
    "```python\n",
    "f = open('text_example.txt', 'a')\n",
    "f.write('this is another line\\n')\n",
    "f.close()\n",
    "```\n",
    "\n",
    "Although this short example shows opening and writing a text file, this approach can be used to edit a wide range of file extensions including .json, .xml, .csv, .tsv, and many more, including binary files in addition to plain text files.\n",
    "\n",
    "## Miscellaneous fun\n",
    "\n",
    "Here are some other bits of basic coding that might be useful. They really show why Python is such a delightful language.\n",
    "\n",
    "You can use unicode characters for variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "096cdd78",
   "metadata": {},
   "outputs": [],
   "source": [
    "α = 15\n",
    "β = 30\n",
    "\n",
    "print(α / β)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "327290b4",
   "metadata": {},
   "source": [
    "You can swap variables in a single assignment:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25ef5cd6",
   "metadata": {},
   "outputs": [],
   "source": [
    "a = 10\n",
    "b = \"This is a string\"\n",
    "\n",
    "a, b = b, a\n",
    "\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a312e666",
   "metadata": {},
   "source": [
    "**itertools** offers counting, repeating, cycling, chaining, and slicing. Here's a cycling example that uses the `next` keyword to get the next iteraction:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77ec6b57",
   "metadata": {},
   "outputs": [],
   "source": [
    "from itertools import cycle\n",
    "\n",
    "lorrys = [\"red lorry\", \"yellow lorry\"]\n",
    "lorry_iter = cycle(lorrys)\n",
    "\n",
    "print(next(lorry_iter))\n",
    "print(next(lorry_iter))\n",
    "print(next(lorry_iter))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1a332e98",
   "metadata": {},
   "source": [
    "**itertools** also offers products, combinations, combinations with replacement, and permutations. Here are the combinations of 'abc' of length 2:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "71f00648",
   "metadata": {},
   "outputs": [],
   "source": [
    "from itertools import combinations\n",
    "\n",
    "print(list(combinations(\"abc\", 2)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e261358e",
   "metadata": {},
   "source": [
    "Find out what the date is! (Can pass a timezone as an argument.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e23ff29",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import date\n",
    "\n",
    "print(date.today())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8311a0b7",
   "metadata": {},
   "source": [
    "Because functions are just objects, you can iterate over them just like any other object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58f95d73",
   "metadata": {},
   "outputs": [],
   "source": [
    "functions = [str.isdigit, str.islower, str.isupper]\n",
    "\n",
    "raw_str = \"asdfaa3fa\"\n",
    "\n",
    "for str_func in functions:\n",
    "    print(f\"Function name: {str_func.__name__}, value is:\")\n",
    "    print(str_func(raw_str))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65cf7e34",
   "metadata": {},
   "source": [
    "Functions can be defined recursively. For instance, the Fibonacci sequence is defined such that $ a_n = a_{n-1} + a_{n-2} $ for $ n>1 $."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64f8a599",
   "metadata": {},
   "outputs": [],
   "source": [
    "def fibonacci(n):\n",
    "    if n < 0:\n",
    "        print(\"Please enter n>0\")\n",
    "        return 0\n",
    "    elif n <= 1:\n",
    "        return n\n",
    "    else:\n",
    "        return fibonacci(n - 1) + fibonacci(n - 2)\n",
    "\n",
    "\n",
    "[fibonacci(i) for i in range(10)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f50370b1",
   "metadata": {},
   "source": [
    "This is just a taster of what can be done using base Python with few extra packages. For more, especially if you've done other chapters in the book already and want to go a bit deeper, see the Chapter on {ref}`code-advanced`.\n",
    "\n",
    "## Review\n",
    "\n",
    "If you now understand a bit about what objects, types, operators, conditional statements, loops, functions, and imports are, and *how* to use them, you are well on your way to becoming a coder. What you've seen in this chapter are the building blocks and you need to know a bit about them. But bricks by themselves do not a house make. In subsequent chapters, we'll see how to combine the elements here to do productive analysis in economics."
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "c4570b151692b3082981c89d172815ada9960dee4eb0bedb37dc10c95601d3bd"
  },
  "jupytext": {
   "cell_metadata_filter": "-all",
   "encoding": "# -*- coding: utf-8 -*-",
   "formats": "md:myst",
   "main_language": "python"
  },
  "kernelspec": {
   "display_name": "Python 3.8.12 64-bit ('codeforecon': conda)",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
